package org.example;
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Model;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.artifact.ProjectArtifact;
import org.apache.maven.project.artifact.ProjectArtifactMetadata;
import org.apache.maven.rtinfo.RuntimeInformation;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.deployment.DeployRequest;
import org.eclipse.aether.deployment.DeploymentException;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.util.artifact.SubArtifact;
import org.eclipse.aether.util.version.GenericVersionScheme;
import org.eclipse.aether.version.Version;
import org.example.dependency.OfflineCopyDependenciesMojo;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * Deploys an artifact with all dependencies to remote repository.
 *
 * @author wangyao
 */
@Mojo(name = "offline-deploy", requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.PROCESS_SOURCES, threadSafe = true)
public class OfflineDeployMojo
        extends OfflineCopyDependenciesMojo {

    private static final String AFFECTED_MAVEN_PACKAGING = "maven-plugin";

    private static final String FIXED_MAVEN_VERSION = "3.9.0";

    private static final Pattern ALT_LEGACY_REPO_SYNTAX_PATTERN = Pattern.compile("(.+?)::(.+?)::(.+)");

    private static final Pattern ALT_REPO_SYNTAX_PATTERN = Pattern.compile("(.+?)::(.+)");
    /**
     * Flag whether Maven is currently in online/offline mode.
     */
    @Parameter(defaultValue = "${settings.offline}", readonly = true)
    private boolean offline;

    /**
     * Parameter used to control how many times a failed deployment will be retried before giving up and failing. If a
     * value outside the range 1-10 is specified it will be pulled to the nearest value within the range 1-10.
     *
     * @since 2.7
     */
    @Parameter(property = "retryFailedDeploymentCount", defaultValue = "1")
    private int retryFailedDeploymentCount;

    @Component
    private RuntimeInformation runtimeInformation;

    @Parameter(defaultValue = "${session}", readonly = true, required = true)
    protected MavenSession session;

    @Component
    protected RepositorySystem repositorySystem;

    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject currentProject;

    @Parameter(defaultValue = "${reactorProjects}", required = true, readonly = true)
    private List<MavenProject> reactorProjects;

    @Parameter(defaultValue = "${plugin}", required = true, readonly = true)
    private PluginDescriptor pluginDescriptor;

    /**
     * Whether every project should be deployed during its own deploy-phase or at the end of the multimodule build. If
     * set to {@code true} and the build fails, none of the reactor projects is deployed.
     * <strong>(experimental)</strong>
     *
     * @since 2.8
     */
    @Parameter(defaultValue = "false", property = "deployAtEnd")
    private boolean deployAtEnd;

    /**
     * Specifies an ALTERNATIVE repository to which the project artifacts should be deployed (other than those specified
     * in &lt;distributionManagement&gt;). <br/>
     * Format: <code>id::url</code>
     * <dl>
     * <dt>id</dt>
     * <dd>The id can be used to pick up the correct credentials from the settings.xml</dd>
     * <dt>url</dt>
     * <dd>The location of the repository</dd>
     * </dl>
     * <b>Note:</b> In version 2.x, the format was <code>id::<i>layout</i>::url</code> where <code><i>layout</i></code>
     * could be <code>default</code> (ie. Maven 2) or <code>legacy</code> (ie. Maven 1), but since 3.0.0 the layout part
     * has been removed because Maven 3 only supports Maven 2 repository layout.
     */
    @Parameter(property = "altDeploymentRepository")
    private String altDeploymentRepository;

    /**
     * The alternative repository to use when the project has a snapshot version.
     *
     * <b>Note:</b> In version 2.x, the format was <code>id::<i>layout</i>::url</code> where <code><i>layout</i></code>
     * could be <code>default</code> (ie. Maven 2) or <code>legacy</code> (ie. Maven 1), but since 3.0.0 the layout part
     * has been removed because Maven 3 only supports Maven 2 repository layout.
     *
     * @see DeployMojo#altDeploymentRepository
     * @since 2.8
     */
    @Parameter(property = "altSnapshotDeploymentRepository")
    private String altSnapshotDeploymentRepository;

    /**
     * The alternative repository to use when the project has a final version.
     *
     * <b>Note:</b> In version 2.x, the format was <code>id::<i>layout</i>::url</code> where <code><i>layout</i></code>
     * could be <code>default</code> (ie. Maven 2) or <code>legacy</code> (ie. Maven 1), but since 3.0.0 the layout part
     * has been removed because Maven 3 only supports Maven 2 repository layout.
     *
     * @see DeployMojo#altDeploymentRepository
     * @since 2.8
     */
    @Parameter(property = "altReleaseDeploymentRepository")
    private String altReleaseDeploymentRepository;

    /**
     * Set this to 'true' to bypass artifact deploy
     * Since since 3.0.0-M2 it's not anymore a real boolean as it can have more than 2 values:
     * <ul>
     *     <li><code>true</code>: will skip as usual</li>
     *     <li><code>releases</code>: will skip if current version of the project is a release</li>
     *     <li><code>snapshots</code>: will skip if current version of the project is a snapshot</li>
     *     <li>any other values will be considered as <code>false</code></li>
     * </ul>
     *
     * @since 2.4
     */
    @Parameter(property = "maven.deploy.skip", defaultValue = "false")
    private String skip = Boolean.FALSE.toString();

    private enum State {
        SKIPPED, DEPLOYED, TO_BE_DEPLOYED
    }

    private static final String DEPLOY_PROCESSED_MARKER = OfflineDeployMojo.class.getName() + ".processed";

    private static final String DEPLOY_ALT_RELEASE_DEPLOYMENT_REPOSITORY =
            OfflineDeployMojo.class.getName() + ".altReleaseDeploymentRepository";

    private static final String DEPLOY_ALT_SNAPSHOT_DEPLOYMENT_REPOSITORY =
            OfflineDeployMojo.class.getName() + ".altSnapshotDeploymentRepository";

    private static final String DEPLOY_ALT_DEPLOYMENT_REPOSITORY =
            OfflineDeployMojo.class.getName() + ".altDeploymentRepository";

    private void putState(State state) {
        getPluginContext().put(DEPLOY_PROCESSED_MARKER, state.name());
    }

    private void putPluginContextValue(String key, String value) {
        if (value != null) {
            getPluginContext().put(key, value);
        }
    }

    private String getPluginContextValue(Map<String, Object> pluginContext, String key) {
        return (String) pluginContext.get(key);
    }

    private State getState(Map<String, Object> pluginContext) {
        return State.valueOf(getPluginContextValue(pluginContext, DEPLOY_PROCESSED_MARKER));
    }

    private boolean hasState(MavenProject project) {
        Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, project);
        return pluginContext.containsKey(DEPLOY_PROCESSED_MARKER);
    }

    @Override
    public void doExecute()
            throws MojoExecutionException {
        initParam();
        super.doExecute();
    }

    @Override
    protected void offlineDeploy(Artifact artifact, File destFile) throws MojoExecutionException {

        MavenProject clone = currentProject.clone();
        clone.setFile(destFile);
        clone.setArtifact(artifact);
        clone.setArtifactId(artifact.getArtifactId());
        clone.setGroupId(artifact.getGroupId());
        clone.setVersion(artifact.getVersion());
        project = clone;

        getLog().info("=========准备上传的依赖========" + project);
        if (Boolean.parseBoolean(skip)
                || ("releases".equals(skip) && !ArtifactUtils.isSnapshot(project.getVersion()))
                || ("snapshots".equals(skip) && ArtifactUtils.isSnapshot(project.getVersion()))
        ) {
            getLog().info("Skipping artifact deployment");
            putState(State.SKIPPED);
        } else {
            failIfOffline();
            warnIfAffectedPackagingAndMaven(project.getPackaging());

            getLog().info("=========准备上传判断========" + deployAtEnd);
            if (!deployAtEnd) {
                deploy(session.getRepositorySession(),
                        processProject(project,
                                altSnapshotDeploymentRepository, altReleaseDeploymentRepository, altDeploymentRepository));

                putState(State.DEPLOYED);
            } else {
                putPluginContextValue(DEPLOY_ALT_RELEASE_DEPLOYMENT_REPOSITORY, altReleaseDeploymentRepository);
                putPluginContextValue(DEPLOY_ALT_SNAPSHOT_DEPLOYMENT_REPOSITORY, altSnapshotDeploymentRepository);
                putPluginContextValue(DEPLOY_ALT_DEPLOYMENT_REPOSITORY, altDeploymentRepository);
                putState(State.TO_BE_DEPLOYED);
                getLog().info("Deferring deploy for " + project.getGroupId()
                        + ":" + project.getArtifactId() + ":" + project.getVersion() + " at end");
            }
        }
        getLog().info("=========上传成功========" );
//        if (allProjectsMarked()) {
//            for (MavenProject reactorProject : reactorProjects) {
//                Map<String, Object> pluginContext = session.getPluginContext(pluginDescriptor, reactorProject);
//                State state = getState(pluginContext);
//                if (state == State.TO_BE_DEPLOYED) {
//                    String altReleaseDeploymentRepository =
//                            getPluginContextValue(pluginContext, DEPLOY_ALT_RELEASE_DEPLOYMENT_REPOSITORY);
//                    String altSnapshotDeploymentRepository =
//                            getPluginContextValue(pluginContext, DEPLOY_ALT_SNAPSHOT_DEPLOYMENT_REPOSITORY);
//                    String altDeploymentRepository =
//                            getPluginContextValue(pluginContext, DEPLOY_ALT_DEPLOYMENT_REPOSITORY);
//
//                    deploy(session.getRepositorySession(),
//                            processProject(reactorProject,
//                                    altSnapshotDeploymentRepository, altReleaseDeploymentRepository, altDeploymentRepository)
//                    );
//                }
//            }
//        }
    }

    private void initParam() {
        super.copyPom = true;
        super.outputDirectory = new File("lib");
        super.project = project;
        super.reactorProjects = reactorProjects;
        super.session = session;
    }

    private boolean allProjectsMarked() {
        for (MavenProject reactorProject : reactorProjects) {
            if (!hasState(reactorProject)) {
                return false;
            }
        }
        return true;
    }

    private DeployRequest processProject(final MavenProject project,
                                         final String altSnapshotDeploymentRepository,
                                         final String altReleaseDeploymentRepository,
                                         final String altDeploymentRepository)
            throws MojoExecutionException/*, MojoFailureException*/ {
        DeployRequest request = new DeployRequest();
        request.setRepository(getDeploymentRepository(project,
                altSnapshotDeploymentRepository, altReleaseDeploymentRepository, altDeploymentRepository));
        System.out.println("============processProject===========316");
        org.apache.maven.artifact.Artifact mavenMainArtifact = project.getArtifact();
        String packaging = project.getPackaging();
        File pomFile = project.getFile();
        boolean isPomArtifact = "pom".equals(packaging);
        boolean pomArtifactAttached = false;

        if (pomFile != null) {
            request.addArtifact(RepositoryUtils.toArtifact(new ProjectArtifact(project)));
            pomArtifactAttached = true;
        }

        if (!isPomArtifact) {
            File file = mavenMainArtifact.getFile();
            if (file != null && file.isFile()) {
                org.eclipse.aether.artifact.Artifact mainArtifact = RepositoryUtils.toArtifact(mavenMainArtifact);
                request.addArtifact(mainArtifact);

                if (!pomArtifactAttached) {
                    for (Object metadata : mavenMainArtifact.getMetadataList()) {
                        if (metadata instanceof ProjectArtifactMetadata) {
                            request.addArtifact(new SubArtifact(
                                    mainArtifact,
                                    "",
                                    "pom"
                            ).setFile(((ProjectArtifactMetadata) metadata).getFile()));
                            pomArtifactAttached = true;
                        }
                    }
                }
            } else if (!project.getAttachedArtifacts().isEmpty()) {
                throw new MojoExecutionException("The packaging plugin for this project did not assign "
                        + "a main file to the project but it has attachments. Change packaging to 'pom'.");
            } else {
                throw new MojoExecutionException("The packaging for this project did not assign "
                        + "a file to the build artifact");
            }
        }

        if (!pomArtifactAttached) {
            throw new MojoExecutionException("The POM could not be attached");
        }

        for (org.apache.maven.artifact.Artifact attached : project.getAttachedArtifacts()) {
            getLog().debug("Attaching for install: " + attached.getId());
            request.addArtifact(RepositoryUtils.toArtifact(attached));
        }

        return request;
    }

    /**
     * Visible for testing.
     */
    RemoteRepository getDeploymentRepository(final MavenProject project,
                                             final String altSnapshotDeploymentRepository,
                                             final String altReleaseDeploymentRepository,
                                             final String altDeploymentRepository)

            throws MojoExecutionException/*, MojoFailureException*/ {
        RemoteRepository repo = null;

        String altDeploymentRepo;
        if (ArtifactUtils.isSnapshot(project.getVersion()) && altSnapshotDeploymentRepository != null) {
            altDeploymentRepo = altSnapshotDeploymentRepository;
        } else if (!ArtifactUtils.isSnapshot(project.getVersion()) && altReleaseDeploymentRepository != null) {
            altDeploymentRepo = altReleaseDeploymentRepository;
        } else {
            altDeploymentRepo = altDeploymentRepository;
        }

        if (altDeploymentRepo != null) {
            getLog().info("Using alternate deployment repository " + altDeploymentRepo);

            Matcher matcher = ALT_LEGACY_REPO_SYNTAX_PATTERN.matcher(altDeploymentRepo);

            if (matcher.matches()) {
                String id = matcher.group(1).trim();
                String layout = matcher.group(2).trim();
                String url = matcher.group(3).trim();

                if ("default".equals(layout)) {
                    getLog().warn("Using legacy syntax for alternative repository. "
                            + "Use \"" + id + "::" + url + "\" instead.");
                    repo = getRemoteRepository(id, url);
                } else {
                    throw new MojoExecutionException(altDeploymentRepo,
                            "Invalid legacy syntax and layout for repository.",
                            "Invalid legacy syntax and layout for alternative repository. Use \""
                                    + id + "::" + url + "\" instead, and only default layout is supported."
                    );
                }
            } else {
                matcher = ALT_REPO_SYNTAX_PATTERN.matcher(altDeploymentRepo);

                if (!matcher.matches()) {
                    throw new MojoExecutionException(altDeploymentRepo,
                            "Invalid syntax for repository.",
                            "Invalid syntax for alternative repository. Use \"id::url\"."
                    );
                } else {
                    String id = matcher.group(1).trim();
                    String url = matcher.group(2).trim();

                    repo = getRemoteRepository(id, url);
                }
            }
        }

        if (repo == null) {
            repo = RepositoryUtils.toRepo(project.getDistributionManagementArtifactRepository());
        }

        if (repo == null) {
            String msg = "Deployment failed: repository element was not specified in the POM inside"
                    + " distributionManagement element or in -DaltDeploymentRepository=id::url parameter";

            throw new MojoExecutionException(msg);
        }

        return repo;
    }

    private void failIfOffline()
    /* throws MojoFailureException*/ {
        if (offline) {
//        支持 offline 模式
//            throw new MojoFailureException( "Cannot deploy artifacts when Maven is in offline mode" );
        }
    }

    /**
     * If this plugin used in pre-3.9.0 Maven, the packaging {@code maven-plugin} will not deploy G level metadata.
     */
    protected void warnIfAffectedPackagingAndMaven(final String packaging) {
        if (AFFECTED_MAVEN_PACKAGING.equals(packaging)) {
            try {
                GenericVersionScheme versionScheme = new GenericVersionScheme();
                Version fixedMavenVersion = versionScheme.parseVersion(FIXED_MAVEN_VERSION);
                Version currentMavenVersion = versionScheme.parseVersion(runtimeInformation.getMavenVersion());
                if (fixedMavenVersion.compareTo(currentMavenVersion) > 0) {
                    getLog().warn("");
                    getLog().warn("You are about to deploy a maven-plugin using Maven " + currentMavenVersion + ".");
                    getLog().warn("This plugin should be used ONLY with Maven 3.9.0 and newer, as MNG-7055");
                    getLog().warn("is fixed in those versions of Maven only!");
                    getLog().warn("");
                }
            } catch (org.eclipse.aether.version.InvalidVersionSpecificationException e) {
                // skip it: Generic does not throw, only API contains this exception
            }
        }
    }

    /**
     * Creates resolver {@link RemoteRepository} equipped with needed whistles and bells.
     */
    protected RemoteRepository getRemoteRepository(final String repositoryId, final String url) {
        RemoteRepository result = new RemoteRepository.Builder(repositoryId, "default", url).build();

        if (result.getAuthentication() == null || result.getProxy() == null) {
            RemoteRepository.Builder builder = new RemoteRepository.Builder(result);

            if (result.getAuthentication() == null) {
                builder.setAuthentication(session.getRepositorySession().getAuthenticationSelector()
                        .getAuthentication(result));
            }

            if (result.getProxy() == null) {
                builder.setProxy(session.getRepositorySession().getProxySelector().getProxy(result));
            }

            result = builder.build();
        }

        return result;
    }

    /**
     * Handles high level retries (this was buried into MAT).
     */
    protected void deploy(RepositorySystemSession session, DeployRequest deployRequest) throws MojoExecutionException {
        getLog().info("=========走到上传啦========");

        int retryFailedDeploymentCounter = Math.max(1, Math.min(10, retryFailedDeploymentCount));
        DeploymentException exception = null;
        for (int count = 0; count < retryFailedDeploymentCounter; count++) {
            try {
                if (count > 0) {
                    getLog().info("Retrying deployment attempt " + (count + 1) + " of "
                            + retryFailedDeploymentCounter);
                }

                repositorySystem.deploy(session, deployRequest);
                exception = null;
                break;
            } catch (DeploymentException e) {
                if (count + 1 < retryFailedDeploymentCounter) {
                    getLog().warn("Encountered issue during deployment: " + e.getLocalizedMessage());
                    getLog().debug(e);
                }
                if (exception == null) {
                    exception = e;
                }
            }
        }
        if (exception != null) {
            throw new MojoExecutionException(exception.getMessage(), exception);
        }
    }

}
